﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Vortex.Drawing {

	public sealed partial class Canvas2D {
		///<summary>Maximal number of pushed transforms for canvas</summary>
		private const int TRANSFORM_STACK_MAX_SIZE = 64;

		///<summary>Current canvas transformation</summary>
		private Transform2D _transform = Transform2D.Empty;
		///<summary>Canvas transformation stack</summary>
		private Transform2D[] _transformStack = new Transform2D[TRANSFORM_STACK_MAX_SIZE];
		///<summary>Size of transformation stack</summary>
		private int _transformStackSize = 0;

		#region Public Transform Interface

		/// <summary>
		/// Gets or sets canvas transformation. This transformation applies for everything
		/// </summary>
		/// <value>The transform.</value>
		public Transform2D Transform {
			get { return _transform; }
			set { _transform = value; }
		}

		/// <summary>
		/// Resets the transform to initial state (stack is untouched).
		/// </summary>
		public void ResetTransform() {
			_transform = Transform2D.Empty;
		}

		/// <summary>
		/// Sets (overrides) the specified transform for canvas.
		/// </summary>
		/// <param name="transform">The transform for override.</param>
		public void SetTransform(ref Transform2D transform) {
			_transform = transform;
		}

		/// <summary>
		/// Applies the specified transform after current canvas transform.
		/// </summary>
		/// <param name="transform">The transform to apply.</param>
		public void ApplyTransform(ref Transform2D transform) {
			Transform2D current = _transform;
			Transform2D.Multiply(ref current, ref transform, out _transform);
		}

		/// <summary>
		/// Applies the specified transform before current canvas transform.
		/// </summary>
		/// <param name="transform">The transform to apply.</param>
		public void ApplyTransformBefore(ref Transform2D transform) {
			Transform2D current = _transform;
			Transform2D.Multiply(ref transform, ref current, out _transform);
		}

		/// <summary>
		/// Sets (overrides) the specified transform for canvas.
		/// </summary>
		/// <param name="transform">The transform for override.</param>
		public void SetTransform(ref ITransform transform) {
			transform.GetTransform(out _transform);			
		}

		/// <summary>
		/// Applies the specified transform after current canvas transform.
		/// </summary>
		/// <param name="transform">The transform to apply.</param>
		public void ApplyTransform(ref ITransform transform) {
			Transform2D current = _transform, temp;
			transform.GetTransform(out temp);
			Transform2D.Multiply(ref current, ref temp, out _transform);
		}

		/// <summary>
		/// Applies the specified transform before current canvas transform.
		/// </summary>
		/// <param name="transform">The transform to apply.</param>
		public void ApplyTransformBefore(ref ITransform transform) {
			Transform2D current = _transform, temp;
			transform.GetTransform(out temp);
			Transform2D.Multiply(ref temp, ref current, out _transform);
		}

		/// <summary>
		/// Pushes the current transform onto transformation stack.
		/// </summary>
		public void PushTransform() {			
			if (_transformStackSize >= TRANSFORM_STACK_MAX_SIZE) {
				throw new Exception("Transformation stack overflow");
			}
			_transformStack[_transformStackSize++] = _transform;
		}

		/// <summary>
		/// Pops the transform. Retrieves it from stack
		/// </summary>
		public void PopTransform() {
			if (_transformStackSize > 0) {
				_transformStackSize -= 1;
				_transform = _transformStack[_transformStackSize];
			} else {
				throw new Exception("Can't pop. Transformation stack is empty");
			}
		}

		/// <summary>
		/// Translates the canvas on specified offset.
		/// </summary>
		/// <param name="offsetX">The offset by X.</param>
		/// <param name="offsetY">The offset by Y.</param>
		public void Translate(float offsetX, float offsetY) {
			Translate(new Vector2(offsetX, offsetY));
		}

		/// <summary>
		/// Translates the canvas on specified offset.
		/// </summary>
		/// <param name="offset">The offset vector.</param>
		public void Translate(Vector2 offset) {
			Transform2D temp;
			Transform2D.CreateTranslation(ref offset, out temp);
			ApplyTransform(ref temp);
		}

		/// <summary>
		/// Rotates the canvas on specified angle in radians.
		/// </summary>
		/// <param name="angleRad">The angle in radians.</param>
		public void Rotate(float angleRad) {
			Transform2D temp;
			Transform2D.CreateRotation(angleRad, out temp);
			ApplyTransform(ref temp);
		}

		/// <summary>
		/// Rotates the canvas on specified angle in radians around origin.
		/// </summary>
		/// <param name="angleRad">The angle in radians.</param>
		/// <param name="origin">The origin of rotation.</param>
		public void Rotate(float angleRad, Vector2 origin) {
			Transform2D temp;
			Transform2D.CreateRotation(angleRad, ref origin, out temp);
			ApplyTransform(ref temp);
		}

		/// <summary>
		/// Rotates the canvas on specified angle in radians around origin.
		/// </summary>
		/// <param name="angleRad">The angle in radians.</param>
		/// <param name="originX">The origin of rotation : X coord.</param>
		/// <param name="originY">The origin Y of rotation : Y coord.</param>
		public void Rotate(float angleRad, float originX, float originY) {
			Rotate(angleRad, new Vector2(originX, originY));
		}

		/// <summary>
		/// Scales canvas for the specified factor agains (0,0) point.
		/// </summary>
		/// <param name="factor">The factor.</param>
		public void Scale(float factor) {
			Scale(new Vector2(factor));
		}

		/// <summary>
		/// Scales canvas against the origin point
		/// </summary>
		/// <param name="factor">The scale factor.</param>
		/// <param name="origin">The origin point.</param>
		public void Scale(float factor, Vector2 origin) {
			Scale(new Vector2(factor), origin);
		}

		/// <summary>
		/// Scales canvas for the specified factor agains the (0,0) point.
		/// </summary>
		/// <param name="factor">The factor.</param>
		public void Scale(Vector2 factor) {
			Transform2D temp;
			Transform2D.CreateScaling(ref factor, out temp);
			ApplyTransform(ref temp);
		}

		/// <summary>
		/// Scales canvas against the origin point
		/// </summary>
		/// <param name="factor">The scale factor.</param>
		/// <param name="origin">The origin point.</param>
		public void Scale(Vector2 factor, Vector2 origin) {
			Transform2D temp;
			Transform2D.CreateScaling(ref factor, ref origin, out temp);
			ApplyTransform(ref temp);
		}

		/// <summary>
		/// Scales canvas against the origin point
		/// </summary>
		/// <param name="factor">The scale factor.</param>
		/// <param name="originX">The origin point X.</param>
		/// <param name="originY">The origin point Y.</param>
		public void Scale(float factor, float originX, float originY) {
			Scale(factor, new Vector2(originX, originY));
		}


		/// <summary>
		/// Scales canvas against the origin point
		/// </summary>
		/// <param name="factor">The factor.</param>
		/// <param name="originX">The origin point X.</param>
		/// <param name="originY">The origin point Y.</param>
		public void Scale(Vector2 factor, float originX, float originY) {
			Scale(factor, new Vector2(originX, originY));
		}

		#endregion
	}
}
